package ch.uzh.ifi.ddis.ds2011.a3;

import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.awt.image.WritableRaster;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;
import java.util.Random;

import javax.imageio.ImageIO;

import ch.uzh.ifi.ddis.ds2011.a3.helper.ImagePixelMap;
import ch.uzh.ifi.ddis.ds2011.a3.helper.PixelMap;

import com.signalcollect.Edge;
import com.signalcollect.ExecutionInformation;
import com.signalcollect.Graph;
import com.signalcollect.StateForwarderEdge;
import com.signalcollect.configuration.ExecutionMode;
import com.signalcollect.javaapi.ExecutionConfiguration;
import com.signalcollect.javaapi.GraphBuilder;
import com.signalcollect.javaapi.IdStateJavaAggregator;

public class KernelConvolution {

	public int number_of_iterations; // Maximal number of
	private int width; // Number of columns in the simulation grid
	private int height; // Number of rows in the simulation grid

	private int[] image_data;

	private ImagePixelMap image;
	private Graph g;

	public KernelConvolution(int rows, int columns, int number_of_iterations,
			int[] initial_state) {
		this.height = rows;
		this.width = columns;
		this.number_of_iterations = number_of_iterations;
		this.image_data = initial_state;

		this.image = new ImagePixelMap("Iamge to process", width, height);
		// THIS WILL INITIALIZE THE PROCESSING GRAPH
		this.init();
	}

	public void init() {
		g = GraphBuilder.withNumberOfWorkers(1).build();

		// initialize vertices from initial_state
		// TODO: add vertexes here !
		
		// initialize edges from initial_state
		// TODO: add the edges here!
		
		
	}


	public void executionStep(boolean showImage) {
		// this is a helper method, designed to start a GUI and printing of
		// running statistics in the console.
		ExecutionInformation stats = g.execute(ExecutionConfiguration
				.withExecutionMode(ExecutionMode.Synchronous()).withStepsLimit(
						1));
		if (showImage) {
			int[] data = new int[height * width];
			Map<Integer, Integer> idValueMap = g.aggregate(new IdStateJavaAggregator<Integer, Integer>());
			for (int id : idValueMap.keySet()) {
				data[id] = idValueMap.get(id);
			}
			image.setData(data);
			while (image.isNotUpdated()) {
				try {
					Thread.sleep(300);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
		System.out.println(stats);
	}

	public void shutDown() {
		g.shutdown();
	}

	/**
	 * utility method to convert grid coordinates to ids. Id starts with 0 in
	 * the top left corner and increments row wise.
	 * 
	 * if x1<x2 then generateVertexId(x1, y) < generateVertexId(x2, y) if y1<y2
	 * then generateVertexId(x, y1) < generateVertexId(x, y2)
	 * 
	 * @param row
	 * @param collumn
	 * @return the unique id for the cell at the specified location
	 */
	private int generateVertexId(int row, int collumn) {
		return row * this.width + collumn;
	}

	/**
	 * Utility that checks if a coordinate is within the grid. All negative
	 * values and all values bigger or equal to the size constraints of the grid
	 * are considered invalid.
	 * 
	 * @param row
	 * @param collumn
	 * @return
	 */
	private boolean isValidCoordinate(int row, int collumn) {
		if (row < 0 || row >= this.height) {
			return false;
		} else if (collumn < 0 || collumn >= this.width) {
			return false;
		}
		return true;
	}

	/**
	 * @param args
	 * @throws FileNotFoundException
	 */
	public static int[] loadImage(String path) {
		// open image
		File imgPath = new File(path);
		BufferedImage bufferedImage;
		try {
			bufferedImage = ImageIO.read(imgPath);
			BufferedImage temp = new BufferedImage(bufferedImage.getWidth(), bufferedImage.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
	        Graphics g = temp.getGraphics();
	        g.drawImage(bufferedImage, 0, 0, null);
	        g.dispose();
	        
	        WritableRaster raster = temp.getRaster();
	        DataBufferByte data   = (DataBufferByte)raster.getDataBuffer();

	        byte[] d = data.getData();
	        int[] dd = new int[d.length];
	        for (int i=0; i <d.length; i++) {
	        	long val = d[i] & 0xFF;
	        	dd[i] = (int)val;
	        }
	        
	        return dd;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
		return null;
	}
	
	public static int getWidth(String path) {
		File f = new File(path);
	    try {
			BufferedImage bufferedImage = ImageIO.read(f);
			return bufferedImage.getWidth();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	    return 80;
	}
	
	public static int getHeight(String path) {
		File f = new File(path);
	    try {
			BufferedImage bufferedImage = ImageIO.read(f);
			return bufferedImage.getHeight();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	    return 80;
	}

	public static void main(String[] args) {
		String path= "datasets/lena.bmp";
				
		int number_of_iterations = 10;
		int width = getWidth(path);
		int height = getHeight(path);
		int[] image_data = loadImage(path);

		if (args.length >= 1) {
			number_of_iterations = Integer.valueOf(args[0]);
		}

		KernelConvolution simulation = new KernelConvolution(height, width, number_of_iterations, image_data);
		for (int i = 0; i < simulation.number_of_iterations; i++) {
			simulation.executionStep(true);
		}
		simulation.shutDown();
		
	}

}
